PupillometryR is a package to pre-process and then analyze simple pupil experiments in R. The best way to download it is to open up R in RStudio, and download it after downloading devtools. It first needs to be downloaded before running, if you have devtools, it can be done directly from the console in R:
devtools::install_github('samhforbes/PupillometryR')
Once we have the package downloaded, we need to load in the package to get started:
library(PupillometryR)
## Loading required package: dplyr
## Warning: package 'dplyr' was built under R version 3.5.3
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
## Loading required package: ggplot2
This package is designed to make dealing with pupil data (perhaps more traditionally done in MATLAB) easier to wrangle in R. It makes heavy use of a few packages which should be acknowledged here, especially the (excellent) packages fda and signal.
As well as the above packages, it is very important to note that the type of analysis shown here has been available in MATLAB for a while, and there is an excellent tutorial on it, which I thoroughly recommend reading first, written by Sylvain Sirois, here:.
It’s worth making sure that your setup and experiment do facilitate the use of pupillometry - it may not be suited for all kinds of experiment.
We will first run through an example analysis with the data provided in the package, which, again, comes from Sylvain Sirois’ tutorial on his webpage.
The first thing I would recommend doing is having a close look at the pupil data. Eyetrackers have a couple of different ways of dealing with this, so it’s important to know a few things:
The data here is an eyetracking experiment with hard and easy trials, performed on 9 participants. Participant 8 needs to be removed (more details on Sylvain’s tutorial).
data("pupil_data")
#Check that IDs are not numeric
pupil_data$ID <- as.character(pupil_data$ID)
#remove participant number 8, who had problematic data
pupil_data <- subset(pupil_data, ID != 8)
#blinks were registered as -1, so replace with NAs
pupil_data$LPupil[pupil_data$LPupil == -1] <- NA
pupil_data$RPupil[pupil_data$RPupil == -1] <- NA
The plotting is also a theme of this tutorial, so I will set a nice theme that makes the plots look pretty:
library(ggplot2)
theme_set(theme_classic(base_size = 12))
First up, we need to put the data into pupillometryR format for further analysis
Sdata <- make_pupillometryr_data(data = pupil_data,
subject = ID,
trial = Trial,
time = Time,
condition = Type)
In the current data, it is not a concern, but there may be a situation where certain timebins are missing from your data. This can be fixed here, and we will look at the raw data:
new_data <- replace_missing_data(data = Sdata)
## Warning in replace_missing_data(data = Sdata): replace_missing_data will
## only help if you have missing timepoints, and a reliable time column.
head(new_data)
## ID Trial Time RPupil LPupil Timebin Type
## 1 1 Easy1 16.66667 3.233615 2.935160 1 Easy
## 2 1 Easy1 33.33333 3.216972 2.919698 2 Easy
## 3 1 Easy1 50.00000 3.205405 2.924402 3 Easy
## 4 1 Easy1 66.66667 3.217505 2.935777 4 Easy
## 5 1 Easy1 83.33333 3.214905 2.928921 5 Easy
## 6 1 Easy1 100.00000 3.211777 2.922048 6 Easy
Equally, if your data is not cut to the time windows that you are interested in, the subset_data function allows trimming. PupillometryR has some built-in plotting functions to allow you to look at certain data types. You simply need to specify a pupil column to display, and how you would like the data displayed in groups. The plots are ggplot items, so can be edited with themes, and arguments such as ylab(). Below we display it first by condition, then by subject:
plot(new_data, pupil = LPupil, group = 'condition')
## Warning: Removed 3639 rows containing non-finite values (stat_summary).
plot(new_data, pupil = LPupil, group = 'subject')
## Warning: Removed 3639 rows containing non-finite values (stat_summary).
PupillometryR offers a few smoothing options to make processing the data a little easier. We’ll do the full set here. A great reference for these is Sylvain’s tutorial, and also Jackson and Sirois, 2009, Dev. Sci. First off, we can regress one pupil against the other to get some measure of smoothing.
regressed_data <- regress_data(data = new_data,
pupil1 = RPupil,
pupil2 = LPupil)
Now that we have done that, we want the mean of the two pupil sizes, so let’s see how that looks:
mean_data <- calculate_mean_pupil_size(data = regressed_data,
pupil1 = RPupil,
pupil2 = LPupil)
plot(mean_data, pupil = mean_pupil, group = 'subject')
## Warning: Removed 3710 rows containing non-finite values (stat_summary).
PupillometryR offers 3 filter types: A hanning window, a low-pass butterworth filter, and a median filter. The low-pass filter can be a little unstable at the beginning and end of each trial, so it’s worth looking at your data to see if it’s appropriate. Here we will use the hanning filter. The degree gives the size of the rolling window.
filtered_data <- filter_data(data = mean_data,
pupil = mean_pupil,
filter = 'hanning',
degree = 11)
## Performing hanning filter
plot(filtered_data, pupil = mean_pupil, group = 'subject')
## Warning: Removed 4141 rows containing non-finite values (stat_summary).
The next step is to interpolate across blinks. Filtering before the interpolation generally allows more sensible interpolation, but the way this is done varies a bit on the data, and you will see examples without this. We can interpolate in this package either linear or cubic, but again, it’s best to always check your data afterwards to make sure it looks the way you might expect. Here we opt for the linear interpolation:
int_data <- interpolate_data(data = filtered_data,
pupil = mean_pupil,
type = 'linear')
## Performing linear interpolation
#plot
plot(int_data, pupil = mean_pupil, group = 'subject')
Baselining the data is a powerful way of making sure we control for between-participant variance of average pupil size. If we are looking at analyses that are largely within-subject, as we do here, this may not be such an issue, but we will do this anyway. This function allows us to baseline to the mean pupil size within a time window. Here we are just taking the first 100ms of the trial. If your baseline period is just outside of your analysis window (which it often will be), you can use subset_data() to remove that after baselining.
base_data <- baseline_data(data = int_data,
pupil = mean_pupil,
start = 0,
stop = 100)
plot(base_data, pupil = mean_pupil, group = 'subject')